# coding=UTF-8

from django.db import models
from django.db.models.aggregates import Max, Count, Sum
import datetime
import re

class TimePeriod:
    DAY = "day"
    WEEK = "week"
    MONTH = "month"
    YEAR = "year"

'''

@author: Behrooz Nobakht
'''
class AdManager(models.Manager):

    def get_default_qs(self):
        from amscms.ams.models import Ad
        return Ad.objects.exclude(enabled=False).filter(
                                  start_date__lte=datetime.datetime.now(),
                                  end_date__gte=datetime.datetime.now())

    def find_ads(self, query):
        words = query.split(" ")
        if not words or len(words) == 0:
            return None
        all = self.get_default_qs().exclude(stats=None).filter(stats__score__gt=0)
        result = all.filter(adtag__term__term__in=words)
        for w in words:
            result = result | all.filter(title__icontains=w)
        for w in words:
            result = result | all.filter(description__icontains=w)
        result = result.distinct()
        result = result.order_by('-stats__score')
        return result

    def find_ads_by_user(self, user, order_by="-start_date"):
        from amscms.ams.models import Ad
        ads = Ad.objects.filter(advertizer__user=user).order_by(order_by)
        return ads

    def find_ads_by_tag(self, tag):
        ads = self.get_default_qs().filter(adtag__term__term=tag).order_by('-stats__score')
        return ads

    def find_ads_by_tag_id(self, tagid):
        ads = self.get_default_qs().filter(adtag__term__id=tagid).order_by('-stats__score')
        return ads

    def find_ads_by_advertizer_id(self, advertizer_id):
        ads = self.get_default_qs().filter(advertizer__id__exact=advertizer_id).order_by('-stats__score')
        return ads

    def find_ads_by_advertizer_title(self, title):
        ads = self.get_default_qs().filter(advertizer__title__exact=title).order_by('-stats__score')
        return ads

    def find_other_ads_by_same_advertizer(self, ad_id, size=10):
        from amscms.ams.models import Ad
        ad = Ad.objects.get_by_id(ad_id)
        others = self.get_default_qs().exclude(id=ad_id).exclude(stats=None).filter(
                        advertizer__id=ad.advertizer.id).distinct().order_by("-stats__score")[0:size]
        return others

    def find_related_ads(self, ad_id, size=10):
        from amscms.ams.models import Ad, AdTag
        terms = AdTag.objects.find_adtag_terms(ad_id)
        ads = Ad.objects.get_default_qs().exclude(stats=None).exclude(id=ad_id).filter(
                        adtag__term__in=terms).distinct().order_by("-stats__score")[0:size]
        return ads

    def find_top_ads(self, order_by="-stats__score", size=10):
        qs = self.get_default_qs()
        if size is None:
            ads = qs.order_by(order_by)
        if size is not None:
            ads = qs.order_by(order_by)[0:size]
        result = []
        if len(ads) == 0:
            return result
        if size is None:
            size = ads.count()
        if size >= ads.count():
            size = ads.count()
        for i in range(0, size):
            if self.is_published(ads[i].id):
                result.append({'ad': ads[i], 'score': ads[i].stats.score})
        return result

    def find_latest_ads(self, period=TimePeriod.DAY, range=None):
        if period == TimePeriod.DAY:
            start = datetime.date.today()
            end = start + datetime.timedelta(days=1)
        if period == TimePeriod.WEEK:
            end = datetime.date.today()
            start = end - datetime.timedelta(days=7)
        if period == TimePeriod.MONTH:
            end = datetime.date.today()
            start = end - datetime.timedelta(days=30)
        if period == TimePeriod.YEAR:
            end = datetime.date.today()
            start = end - datetime.timedelta(days=365)
        if range is not None:
            start = range[0]
            end = range[1]
        return self.get_default_qs().filter(
                    start_date__range=(start, end)).order_by("-start_date")
        return None

    def find_published_ads_count(self):
        return self.get_default_qs().count()

    def find_registered_ads_count(self):
        return self.all().count()

    def get_by_id(self, id):
        ads = self.filter(id=id)
        if ads is None or len(ads) == 0:
            return None
        return ads[0]

    def get_by_uuid(self, uuid):
        return self.get(pk=uuid)

    def owns(self, id, user):
        ad = self.get_by_id(id)
        if user == ad.advertizer.user:
            return True
        return False

    def register_ad(self, ad, tags, user):
        from amscms.ams.models import Ad, AdOrder, AdStats
        from amscms.core.utils import uuids
        ad_uuid, hex = uuids.generate_uuid1()
        key = uuids.generate_key()
        ad.id = key
        ad.uuid = hex
        ad.save()
        if ad.id is None:
            return None
        saved_tags = ad.save_tags(tags)
        if not saved_tags:
            return None
        AdStats.objects.init(ad)
        cost, discount = self.calculate_ad_cost(ad, user)
        ad_order = AdOrder(ad=ad, amount=cost)
        ad_order.save()
        if ad_order.id is None:
            return None
        return ad_order

    def extract_ad_tags(self, form):
        tags_string = form.cleaned_data["tags"]
        del form.cleaned_data["tags"]
        pattern = re.compile(u'[,،\s]+')
        tags = pattern.split(tags_string)
        return tags

    def create_ad(self, form, user):
        from amscms.ams.models import Ad
        tags = self.extract_ad_tags(form)
        ad = Ad(**form.cleaned_data)
        ad.title = ad.title.strip()
        ad.registration_date = datetime.datetime.now()
        return ad, tags

    def update_ad(self, id, form, user):
        from amscms.ams.models import AdOrder
        from amscms.payment.models import CreditDepositHistory
        old_ad = self.get_by_id(id)
        try:
            ad = form.save()
            tags = self.extract_ad_tags(form)
            ad.save_tags(tags)
            amount = self.calculate_update_cost(ad, old_ad)
            adorder = None
            deposit = None
            if amount == 0:
                # nothing
                pass
            elif amount > 0:
                adorder = AdOrder(ad=ad, amount=amount)
                adorder.save()
                self.disable_ad(ad)
            else:
                deposit = CreditDepositHistory.objects.deposit_credit(user, -amount)
            return ad, adorder, deposit
        except:
            return None

    def delete_ad(self, id, user):
        from amscms.payment.models import CreditDepositHistory
        ad = self.get_by_id(id)
        diff = ad.end_date - datetime.date.today()
        deposit = None
        if diff.days > 0:
            amount = diff.days * 10
            deposit = CreditDepositHistory.objects.deposit_credit(user, amount)
        ad.delete()
        return deposit

    def enable_ad(self, ad):
        ad.enabled = True
        ad.save()
        return ad

    def disable_ad(self, ad):
        ad.enabled = False
        ad.save()
        return ad

    def is_published(self, id):
        from amscms.ams.models import AdOrder
        ad = self.get_by_id(id)
        if not ad.enabled:
            return False
        if ad.start_date > datetime.date.today():
            return False
        if ad.end_date < datetime.date.today():
            return False
        if not AdOrder.objects.is_paid(id):
            return False
        return True

    def calculate_ad_cost(self, ad, user):
        from amscms.ams.models import AdPricing
        cost, discount = AdPricing.objects.calculate_ad_cost(ad, user)
        return cost, discount

    def calculate_update_cost(self, new_ad, old_ad):
        from amscms.ams.models import AdPricing
        cost = AdPricing.objects.calculate_ad_update_cost(new_ad, old_ad)
        return cost

class AdvertizerManager(models.Manager):

    def find_advertizers_by_user(self, user):
        from amscms.ams.models import Advertizer
        advertizers = Advertizer.objects.filter(user=user)
        return advertizers

    def find_top_investor_advertizers(self, size=10):
        from amscms.ams.models import Advertizer
        advs = Advertizer.objects.annotate(ind_sum=Sum("ad__adorder__amount")).order_by("-ind_sum")[0:size]
        result = []
        if size >= advs.count():
            size = advs.count()
        for i in range(0, size):
            result.append({'advertizer': advs[i], 'total': advs[i].ind_sum})
        return result

    def find_top_registrar_advertizers(self, size=10):
        from amscms.ams.models import Advertizer
        advs = Advertizer.objects.annotate(ad_count=Count("ad__id")).order_by("-ad_count")[0:size]
        result = []
        if size >= advs.count():
            size = advs.count()
        for i in range(0, size):
            result.append({'advertizer': advs[i], 'total': advs[i].ad_count})
        return result

    def can_register(self, dict, user):
        title = dict['title']
        title = title.strip()
        similars = self.filter(title=title)
        if similars is None or len(similars) == 0:
            return True
        return False

'''

@author: Behrooz Nobakht
'''
class AdTagManager(models.Manager):

    def find_adtags(self, ad_id):
        from amscms.ams.models import AdTag
        result = AdTag.objects.filter(ad__id=ad_id, term__enabled=True).order_by("-term__frequency")
        return result

    def find_adtag_terms(self, ad_id):
        from amscms.dict.models import Term
        terms = Term.objects.exclude(enabled=False).filter(adtag__ad__id=ad_id).order_by("-frequency")
        return terms

    def save_or_update(self, ad, term):
        from amscms.ams.models import AdTag
        existing = AdTag.objects.filter(ad=ad, term=term)
        if existing is None or len(existing) == 0:
            tag = AdTag(ad=ad, term=term)
            tag.save()

'''

@author: Behrooz Nobakht
'''
class AdLikeManager(models.Manager):

    def user_scored_ad(self, the_user, ad_id):
        from amscms.ams.models import Ad, AdLikeDislike
        if the_user.is_anonymous():
            return False
        the_ad = Ad.objects.get_by_id(ad_id)
        likes = AdLikeDislike.objects.filter(user=the_user, ad=the_ad)
        if likes is None or len(likes) == 0 or len(likes) > 1:
            return False
        if len(likes) == 1:
            return True
        return False

    def count_likes(self, ad_id):
        from amscms.ams.models import AdLikeDislike
        likes = AdLikeDislike.objects.filter(ad__id=ad_id, score=1)
        if likes is None:
            return 0
        return len(likes)

    def count_dislikes(self, ad_id):
        from amscms.ams.models import AdLikeDislike
        dislikes = AdLikeDislike.objects.filter(ad__id=ad_id, score= -1)
        if dislikes is None:
            return 0
        return len(dislikes)

class AdViewManager(models.Manager):

    def add_adview(self, ad_id, the_user, referer=None, viewer_ip=None):
        from amscms.ams.models import Ad, AdView
        ad = Ad.objects.get_by_id(ad_id)
        adview = AdView(ad=ad, user=the_user, referer=referer, viewer_ip=viewer_ip)
        adview.save()
        return adview

    def count_views(self, ad_id):
        from amscms.ams.models import AdView
        result = AdView.objects.filter(ad__id=ad_id).aggregate(count_views=Count('id'))
        return result['count_views']

    def find_last_viewed_time(self, ad_id):
        from amscms.ams.models import AdView
        result = AdView.objects.filter(ad__id=ad_id).aggregate(last_viewed=Max('last_modified'))
        return result['last_viewed']

class AdStatsManager(models.Manager):

    def init(self, ad):
        from amscms.ams.models import AdStats
        stats = AdStats(ad=ad, views=0, likes=0, dislikes=0, score=0)
        stats.save()
        ad.stats = stats
        ad.save()

    def find_by_ad_id(self, ad_id):
        from amscms.ams.models import Ad, AdStats
        the_ad = Ad.objects.get_by_id(ad_id)
        stats = AdStats.objects.filter(ad__id=ad_id)
        #current_views = AdView.objects.count_views(ad_id)
        #current_likes = AdLikeDislike.objects.count_likes(ad_id)
        #current_dislikes = AdLikeDislike.objects.count_dislikes(ad_id)
        if stats is None or len(stats) < 1:
            stats = AdStats(ad=the_ad, views=0, likes=0, dislikes=0, score=0)
            stats.save()
            the_ad.stats = stats
            the_ad.save()
        else:
            stats = stats[0]
            the_ad.stats = stats
            the_ad.save()
        return stats

    def update_like_stats(self, ad_id):
        stats = self.find_by_ad_id(ad_id)
        stats.likes = stats.likes + 1
        stats.update_score()
        return stats

    def update_dislike_stats(self, ad_id):
        stats = self.find_by_ad_id(ad_id)
        stats.dislikes = stats.dislikes + 1
        stats.update_score()
        return stats

    def update_views_stats(self, ad_id):
        stats = self.find_by_ad_id(ad_id)
        stats.views = stats.views + 1
        stats.update_score()
        return stats

    def find_total_ad_views(self):
        from amscms.ams.models import AdStats
        result = AdStats.objects.all().aggregate(total_views=Sum("views"))
        return result["total_views"]

class AdTagExploreHistoryManager(models.Manager):

    def add_history(self, user, term_id, referer=None, viewer_ip=None):
        from amscms.ams.models import AdTagExploreHistory
        from amscms.dict.models import Term
        the_term = Term.objects.get(pk=term_id)
        the_user = None if user.is_anonymous() else user
        hist = AdTagExploreHistory(user=the_user, term=the_term, viewed_on=datetime.datetime.now(), referer=referer, viewer_ip=viewer_ip)
        hist.save()

class AdSearchHistoryManager(models.Manager):

    def add_history(self, user, query):
        from amscms.ams.models import AdSearchHistory
        from amscms.dict.models import Term
        terms = query.split(" ")
        if terms is None or len(terms) == 0:
            return
        the_user = None if user.is_anonymous() else user
        the_time = datetime.datetime.now()
        for t in terms:
            the_term = Term.objects.find_or_create_search_term(t)
            if the_term is None:
                continue
            hist = AdSearchHistory(user=the_user, term=the_term, viewed_on=the_time)
            hist.save()

class AdOrderManager(models.Manager):

    def find_ad_orders(self, user, order_by="-paid"):
        from amscms.ams.models import AdOrder
        if user.is_anonymous():
            return []
        orders = AdOrder.objects.filter(ad__advertizer__user=user).order_by(order_by)
        return orders

    def find_ad(self, adorder_id):
        from amscms.ams.models import AdOrder
        order = AdOrder.objects.get(pk=adorder_id)
        return order.ad

    def owns(self, id, user):
        from amscms.ams.models import AdOrder
        ad = AdOrder.objects.get(pk=id).ad
        if user == ad.advertizer.user:
            return True
        return False

    def is_paid(self, ad_id):
        orders = self.filter(ad__id=ad_id)
        if orders is None or len(orders) == 0 or len(orders) > 1:
            return False
        order = orders[0]
        return order.paid

class AdPricingManager(models.Manager):

    def get_default_pricing(self):
        try:
            return self.filter(enabled=True, is_default=True)[0]
        except:
            from amscms.ams.models import AdPricing
            pricing = AdPricing(enabled=True, is_default=True, daily_rate=100)
            pricing.save()
            return pricing

    def calculate_ad_cost(self, ad, user):
        pricing = self.get_default_pricing()
        days = self.calculate_ad_days(ad)
        cost = days * pricing.daily_rate
        discount = self.calcuate_ad_cost_discount_rate(ad, user, pricing)
        cost = cost - (cost * (discount / 100.00))
        return cost, discount

    def calculate_ad_update_cost(self, new_ad, old_ad):
        """
        Currently, we do not consider discount rates in updates.
        """
        old_days = old_ad.end_date - old_ad.start_date
        new_days = new_ad.end_date - new_ad.start_date
        days = new_days.days - old_days.days
        pricing = self.get_default_pricing()
        cost = days * pricing.daily_rate
        return cost

    def calcuate_ad_cost_discount_rate(self, ad, user, pricing):
        days = self.calculate_ad_days(ad)
        discount = 0
        if pricing.min_days_discount is not None and days > pricing.min_days_discount:
            remaining = days - pricing.min_days_discount
            start_rate = pricing.min_days_discount_rate
            while start_rate > 0 and remaining > 0:
                discount = discount + start_rate
                start_rate = start_rate - 2
                remaining = remaining - pricing.min_days_discount_forevery
        if pricing.range_discount_start is not None:
            start_diff = ad.start_date - pricing.range_discount_start
            end_diff = ad.end_date - pricing.range_discount_end
            dis_diff = end_diff - start_diff
            if dis_diff.days > 0:
                start_rate = pricing.range_discount_rate
                remaining = dis_diff.days
                while start_rate > 0 and remaining > 0:
                    discount = discount + start_rate
                    start_rate = start_rate - 2
                    remaining = remaining - pricing.range_discount_forevery
        return discount

    def calculate_ad_days(self, ad):
        diff = ad.end_date - ad.start_date
        days = diff.days
        return days

class AdProfileManager(models.Manager):

    def exists_value(self, ad_id, key):
        from amscms.ams.models import Ad
        ad = Ad.objects.get_by_id(ad_id)
        current = self.filter(ad=ad, key=key)
        if current is None or len(current) == 0:
            return None
        return current[0]

    def save_value(self, ad_id, key, value):
        from amscms.ams.models import Ad, AdProfile
        current = self.exists_value(ad_id, key)
        if current is None:
            ad = Ad.objects.get_by_id(ad_id)
            profile = AdProfile(ad=ad, key=key, value=value)
            profile.save()
        else:
            current.value = value
            current.save()


